package com.imaginaryphysics.www.simplepairs;


/**
 * This class constructs and holds an association of paired Objects. The association created is such that each object is matched against every other object
 * as a Pair. For instance given object A , B , and C it will create a Pair object for each possible match Pair(A,B) Pair(A,C) Pair(B,C)
 * 
 * @author imaginaryMan
 * @param <T> The type of object that you are creating a relationship between.
 */


/**
 * 
 *  
 */

public class SimplePairs <T extends Pairable> {
	
	private Pair<?>[] pairs;
	private T[] objects;
	
	private int number_of_pairs=-1;
	private int number_of_objects;
	
	
	/**
	 * Construct an association of  objects.  An association is a system where each object is paired against each other object. 
	 * @param objects The array of objects that you are organizing into an association. All objects must be of the same class and extend Pairable.
	 */
	
	public SimplePairs(T[] objects){
		this.objects = objects;
		number_of_objects = objects.length;
		get_number_of_pairs();
		pairs = new Pair[get_number_of_pairs()];
		create_pairs();
		
	}
	
	/**
	 * A function to get the number of pairings contained in this SimplePairs object.
	 * @return The number of pairings.
	 */
	
	public int get_number_of_pairs(){
		if(number_of_pairs==-1){	
				  int result=0;
				  for(int increment=0;increment<number_of_objects;increment++) {
				    result+= number_of_objects - ( number_of_objects - increment);
				  }
				  number_of_pairs = result;
		}
		return number_of_pairs;
	}
	
	private void create_pairs(){
		int index = 0;
		int trigger=1;
		int total  = number_of_objects;
		int total_pairs = number_of_pairs;
		
		for(int x=0;x<total_pairs;x++){
			pairs[x] = new Pair<T> (objects[index],objects[trigger]);
			trigger++;
			if(trigger == total){
				index ++;
				trigger = index+1;}
			}	
		}
		
	/**
	 * Returns every Pairing of the given object.
	 * 
	 * @param object The object who's pairings you would like returned.
	 * @return The array of pairings an object belongs to.
	 */
	
	public Pair<?>[] get_set(T object){		
		if(object.sets_created()){
			System.out.println("Set exsists");
			return object.get_set();
		}
		Pair<?>[] set = new Pair[number_of_objects-1];
		int index = 0;
		
			for(int x=0; x<number_of_pairs;x++ ){
				if(pairs[x].is_part_of_pair(object)){
					set[index]=pairs[x];
					index++;
				}
			}
			
		object.set_set(set);
		return set;
	}
	
	public Pair<?>[] get_pairs(){
		return pairs;
	}
	
	
	/*
	 * 
	 * I can't figure out any way to implement these with out Reflection which I am trying to hold back
	 * Since I am still learning Type casting!!!!
	 * 
	 * I figured out the reflection bit and I think this is library is all good and structured
	 * but in case you can`t tell I just started throwing generic`s all over the place
	 * ::Raptor falls from sky::
	 */
	
	/**
	 * Runs the associated calculation on all pairs.
	 */
	
	
	public void batch_calculate(){
	for(Pair<?> pair:pairs){
		pair.calculate();
		}
	}
	
	/**
	 * Returns an array of the type Result_block containing a Result_block for every pairing.
	 * 
	 * @return An array of every Result object associated with your pairings.
	 */
	
	public Result_block[] batch_result(){
		Result_block[]  output= new Result_block[number_of_pairs];
		for(int x=0; x<number_of_pairs;x++){
			output[x] = pairs[x].get_result();
		}
		return output;
	}
	
	/**
	 * Attach a calculation and result to every pair. Created a new Calculation and Result object for each pair.
	 * 
	 * @param calculation Fully qualified path to your calculation class.
	 * @param result Fully qualified path to your result class.
	 */
	
	@SuppressWarnings("unchecked")
	public void batch_set_relationship(String calculation,String result){
		Class<?> calculation_class = null;
		Class<?> calculation_class_super = null;
		
		Class<?> result_class = null;
		Class<?> result_class_super = null;

		
		try {
			calculation_class = Class.forName(calculation);
		} catch (ClassNotFoundException e) {
			// TODO Auto-generated catch block
			System.out.println("The class "+calculation+" can not be found!");
			e.printStackTrace();
		}
		
		try {
			calculation_class_super = Class.forName("com.imaginaryphysics.www.simplepairs.Calculation");
		} catch (ClassNotFoundException e) {
			// TODO Auto-generated catch block
			System.out.println("An internal error has occured because this library is terrible =) Calculation");
			e.printStackTrace();
		}
		
		try {
			result_class = Class.forName(result);
		} catch (ClassNotFoundException e) {
			// TODO Auto-generated catch block
			System.out.println("The class "+result+"could not be found!");
			e.printStackTrace();
		}
		
		try{
			result_class_super = Class.forName("com.imaginaryphysics.www.simplepairs.Result_block");
		} catch (ClassNotFoundException e) {
			// TODO Auto-generated catch block
			System.out.println("An internal error has occured because this library is terrible =) Result");
			e.printStackTrace();
		}
		
		
		if(calculation_class.getSuperclass() != calculation_class_super){
			throw new Improper_class_exception(calculation_class.toString(),calculation_class_super.toString());
		}
		
		
			boolean uses_interface=false;
			Class<?>[] interfaces = result_class.getInterfaces();
			int number_of_interfaces = interfaces.length;
			int iterations =0;
			
			while( iterations < number_of_interfaces){
				if( interfaces[iterations] == result_class_super){
					uses_interface = true;
				}
				iterations++;
			}
				if(!uses_interface){
					throw new Improper_class_exception( result_class.toString() , result_class_super.toString() );
				}
							
		for(Pair<?> pair:pairs){
			try {
				pair.create_relationship((Calculation)calculation_class.newInstance(),(Result_block)result_class.newInstance());
			} catch (InstantiationException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} catch (IllegalAccessException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		
	}
	
	/**
	 * 
	 * @param an object of the subclass pairable
	 * @return the set of objects paired to parameter object
	 */
	
	public Pair<?>[] get_pairings(Pairable object){
		return object.get_set();	
	}
	
	public T[] get_objects(){
		return objects;
	}
	
}